use core::{mem, ops::{Deref, DerefMut}};

use mlibs::{cortex_m::{asm::bootload, interrupt::free, peripheral::SYST}, periph::{rcc::RCC, NVIC}};


const OTA_INFO_ADDR: u32 = 0x20000000;

pub struct __OtaInfo {
    pub ota_addr: u32,
    pub pkg_size: usize,
    pub app_addr: u32,

    ota_addr_: u32,
    pkg_size_: usize,
    app_addr_: u32,
}

pub struct OtaInfo;

impl Deref for OtaInfo {
    type Target = __OtaInfo;
    fn deref(&self) -> &Self::Target {
        unsafe { &*((OTA_INFO_ADDR as *const __OtaInfo)) }
    }
}

impl DerefMut for OtaInfo {
    
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { &mut *(OTA_INFO_ADDR as *mut __OtaInfo) }
    }
}

impl OtaInfo {

    pub fn save(&mut self, ota_addr: u32, pkg_size: usize, app_addr: u32) {
        self.ota_addr = ota_addr;
        self.ota_addr_ = !ota_addr;
        self.pkg_size = pkg_size;
        self.pkg_size_ = !pkg_size;
        self.app_addr = app_addr;
        self.app_addr_ = !app_addr;
    }

    pub fn verify(&self) -> bool {
        (self.ota_addr == !self.ota_addr_)
        && (self.pkg_size == !self.pkg_size_)
        && (self.app_addr == !self.app_addr_)
        && (self.pkg_size > 1024)
    }

    pub fn destroy(&mut self) {
        self.ota_addr = 0;
        self.ota_addr_ = 0;
        self.pkg_size = 0;
        self.pkg_size_ = 0;
        self.app_addr = 0;
        self.app_addr_ = 0;
    }
}

fn disable_all_irqn() {
    free(|_| {
        let nvic: NVIC = unsafe {
            mem::transmute(())
        };
        for reg in nvic.icer.iter() {
            unsafe { reg.write(0xFFFFFFFF); }
        }
        for reg in nvic.icpr.iter() {
            unsafe { reg.write(0xFFFFFFFF); }
        }
        let sys: SYST = unsafe {
            mem::transmute(())
        };
        unsafe {
            sys.csr.write(0);
            RCC.ahbenr.reset();
            RCC.apb1enr.reset();
            RCC.apb2enr.reset();
        }
    });
}

pub fn bootload_with_disable_irq(addr: u32) {
    disable_all_irqn();
    unsafe {
        bootload(addr as _);
    }
}
